﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace Sustainalytics.Common.Hosting
{
    public abstract class ServiceApplication : IDisposable
    {
        protected ServiceContainer _runtimeServiceContainer;
        private bool _disposed = false;
        private ServiceApplicationStateEnum _state = ServiceApplicationStateEnum.Created;
        
        protected List<IServiceDefinition> _serviceDefinitions = new List<IServiceDefinition>(5);
        private IBasicLogger _simpleLogger;

        public EventHandler<IServiceDefinition> ServiceStarting;
        public EventHandler<IServiceDefinition> ServiceStarted;
       
        
        public ServiceApplication() : this(new BasicConsoleLogger())
        {
        }

        public ServiceApplication(IBasicLogger simpleLogger)
        {
            this._simpleLogger = simpleLogger;
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        }
        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs args)
        {
            Exception e = (Exception)args.ExceptionObject;
            this.OnUnhandledDomainException(e);
        }

        public void EnsureInitialized()
        {
            if (this._state != ServiceApplicationStateEnum.Created)
                return;
            
            this._runtimeServiceContainer = new ServiceContainer();

            this._state = ServiceApplicationStateEnum.Initialized;
            AddStartupServices();
        }

        /// <summary>
        /// Add here the startup services to the ServiceProvider; 
        /// </summary>
        protected virtual void AddStartupServices()
        {
            
        }

        protected void RegisterService(IServiceDefinition definition)
        {
            _serviceDefinitions.Add(definition);
        }

        /// <summary>
        /// The type of the service interfaces.
        /// </summary>
        protected IEnumerable<IServiceDefinition> ServicesDefinitions { get { return _serviceDefinitions; } }

        public void Start()
        {
            this.EnsureInitialized();

            if (this._state != ServiceApplicationStateEnum.Initialized)
                return;
            
            this._state = ServiceApplicationStateEnum.Starting;
            
          
            OnStarting();
            
          
            var servicesDefs = this.ServicesDefinitions;

            try
            {

                if (servicesDefs != null)
                {
                    foreach (var serviceDef in servicesDefs)
                    {
                        RaiseServiceStarting(serviceDef);
                        _runtimeServiceContainer.Services.Add(serviceDef.Start());
                        RaiseServiceStarted(serviceDef);
                    }
                }
            }
            catch (Exception ex)
            {
                this._simpleLogger.Log(EventLogEntryType.Error, string.Format("Failed to start application. \n{0}", GetInfo(ex, true)));
                this._state = ServiceApplicationStateEnum.Faulted;
                if(!HandleStartException(ex))
                    throw;
            }

            OnStarted();

            this._state = ServiceApplicationStateEnum.Started;
        }

        private void RaiseServiceStarted(IServiceDefinition serviceDef)
        {
            this._simpleLogger.Log(EventLogEntryType.Information, string.Format("[ready] {0}", serviceDef.GetShortDescription()));

            if(this.ServiceStarted != null)
            {
                this.ServiceStarted(this, serviceDef);
            }
        }

        private void RaiseServiceStarting(IServiceDefinition serviceDef)
        {
            this._simpleLogger.Log(EventLogEntryType.Information, string.Format("[starting] {0} ...", serviceDef.GetShortDescription()));
            if (this.ServiceStarting != null)
            {
                this.ServiceStarting(this, serviceDef);
            }
        }

        protected virtual void OnStarted()
        {
            this._simpleLogger.Log(EventLogEntryType.Information, "[ready] application");
        }

        /// <summary>
        /// Executes when the Start method is called, before anything else
        /// </summary>
        protected virtual void OnStarting()
        {
            this._simpleLogger.Log(EventLogEntryType.Information, "[starting] application ...");
        }

        protected abstract void OnUnhandledDomainException(Exception e);

        protected abstract bool HandleStartException(Exception e);

        protected abstract bool HandleStopException(Exception e);

        public void Stop()
        {
            if (this._state == ServiceApplicationStateEnum.Stoped || this._state == ServiceApplicationStateEnum.Stoping)
                return;

            this._state = ServiceApplicationStateEnum.Stoping;
            OnStoping();
            try
            {
                if (_runtimeServiceContainer != null)
                {
                    _runtimeServiceContainer.Dispose();
                    _runtimeServiceContainer = null;
                }
            }
            catch (Exception ex)
            {
                this._simpleLogger.Log(EventLogEntryType.Error, string.Format("Failed to stop application. \n{0}", GetInfo(ex, true)));
                this._state = ServiceApplicationStateEnum.Faulted;
                if(!HandleStopException(ex))
                    throw;
            }

            OnStoped();
            this._state = ServiceApplicationStateEnum.Stoped;
        }

        protected virtual void OnStoping()
        {

        }
        
        protected virtual void OnStoped()
        {

        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (this._disposed)
                return;

            this._disposed = true;

            if (disposing)
            {
                Stop();
            }
        }

        protected static string GetInfo(Exception exception, bool includeInnerExceptions)
        {
            StringBuilder sb = new StringBuilder();
            Exception currentException = exception;
            string separator = "";
            while (currentException != null)
            {
                sb.Append(string.Format("{2}Exception:\"{0}\" StackTrace:\"{1}\"\n", currentException.Message, currentException.StackTrace, separator));
                if (!includeInnerExceptions)
                    break;
                separator = "\t";

                currentException = currentException.InnerException;
            }
            return sb.ToString();
        }
    }


    public enum ServiceApplicationStateEnum
    {
        Created,
        Initialized,
        
        Started,
        Starting,

        Stoping,
        Stoped,

        Faulted,
    }
  
  
}
